package com.daffodil.core.jdbc;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import com.daffodil.util.JacksonUtils;
import com.daffodil.util.StringUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.PropertyNamingStrategyBase;

import lombok.extern.slf4j.Slf4j;

/**
 * 达佛多数据源配置注册
 * @author yweijian
 * @date 2021年9月1日
 * @version 1.0
 * @description
 */
@Slf4j
public class DaffodilDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware{

	//默认主数据源
	private static final String DATASOURCE_DEFALUT_KEY = "primary";
		
	private static final String DATASOURCE_PREFIX_KEY = "daffodil.datasource";
	
	private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
	
	private static final String JASYPT_ENCRYPTOR_PREFIX_KEY = "jasypt.encryptor";
	
	public static final PropertyNamingStrategy MIDDLE_SNAKE_CASE = new middleSnakeCaseStrategy();
	
	private static ObjectMapper mapper = new ObjectMapper();

	static {
		// 如果存在未知属性，则忽略不报错
		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		// 允许key没有双引号
		mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
		// 允许key有单引号
		mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
		// 允许整数以0开头
		mapper.configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
		// 允许字符串中存在回车换行控制符
		mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
		// 允许中直线字符串转成驼峰式对象
		mapper.setPropertyNamingStrategy(MIDDLE_SNAKE_CASE);
	}
	
	private DataSource defaultDataSource = null;
	
	private Map<String, DataSource> daffodilDataSources = new HashMap<String, DataSource>();

	@SuppressWarnings("rawtypes")
	@Override
	public void setEnvironment(Environment environment) {
		
		PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
		encryptor.setConfig(this.getPBEConfig(environment));
		
		Map<String, HashMap> dataSourceMap = Binder.get(environment).bind(DATASOURCE_PREFIX_KEY, Bindable.mapOf(String.class, HashMap.class)).get();
		if(dataSourceMap != null && !dataSourceMap.isEmpty()) {
			dataSourceMap.keySet().forEach(lookupKey -> {
				DataSourceProperties dataSourceProperties = JacksonUtils.toJavaObject(dataSourceMap.get(lookupKey), DataSourceProperties.class, mapper, null);
				//目前暂且处理这3个敏感的参数
				dataSourceProperties.setUrl(this.getPropertyValue(encryptor, dataSourceProperties.getUrl()));
				dataSourceProperties.setUsername(this.getPropertyValue(encryptor, dataSourceProperties.getUsername()));
				dataSourceProperties.setPassword(this.getPropertyValue(encryptor, dataSourceProperties.getPassword()));
				
				DataSource dataSource = this.buildDataSource(dataSourceProperties);
				//数据源放到动态数据源里
				daffodilDataSources.put(lookupKey, dataSource);
				//构建默认主数据源
				if(DATASOURCE_DEFALUT_KEY.equals(lookupKey)) {
					defaultDataSource = dataSource;
				}
			});
		}
	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		genericBeanDefinition.setBeanClass(DaffodilDataSource.class);
		genericBeanDefinition.setSynthetic(true);

		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
		targetDataSources.putAll(daffodilDataSources);
		for (String key : daffodilDataSources.keySet()) {
			DaffodilDataSourceContextHolder.dataSourceLookupKeys.add(key);
		}

		MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
		propertyValues.addPropertyValue("defaultTargetDataSource", defaultDataSource);
		propertyValues.addPropertyValue("targetDataSources", targetDataSources);

		registry.registerBeanDefinition("datasource", genericBeanDefinition);
	}

	/**
	 * 构建数据源
	 * @param dataSourceProperties
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private DataSource buildDataSource(DataSourceProperties dataSourceProperties) {
		try {
			Class<? extends DataSource> dataSourceType = dataSourceProperties.getType();
			if(null == dataSourceType) {
				dataSourceType = (Class<? extends DataSource>) Class.forName(DATASOURCE_TYPE_DEFAULT);
			}
			DataSourceBuilder<?> factory = DataSourceBuilder.create()
					.driverClassName(dataSourceProperties.getDriverClassName())
					.url(dataSourceProperties.getUrl())
					.username(dataSourceProperties.getUsername())
					.password(dataSourceProperties.getPassword())
					.type(dataSourceType);
			return factory.build();
		} catch (ClassNotFoundException e) {
			log.error(e.getMessage(), e);
		}
		return null;
	}
	
	/**
	 * 获取属性值，有些属性值需要解密比如用户名、密码
	 * @param config
	 * @param value
	 * @return
	 */
	private String getPropertyValue(PooledPBEStringEncryptor encryptor, String value) {
		if(StringUtils.isEmpty(value)) {
			return value;
		}
		Pattern pattern = Pattern.compile("ENC\\((.*)\\)");
		Matcher matcher = pattern.matcher(value);
		if(matcher.find()) {
			value = matcher.group(1);
			value = encryptor.decrypt(value);
		}
		return value;
	}
	
	/**
	 * jasyp加解密配置
	 * @param environment
	 * @return
	 */
	private SimpleStringPBEConfig getPBEConfig(Environment environment) {
		Map<String, String> jasyptMap = Binder.get(environment).bind(JASYPT_ENCRYPTOR_PREFIX_KEY, Bindable.mapOf(String.class, String.class)).get();
		SimpleStringPBEConfig config = new SimpleStringPBEConfig();
		config.setPassword(jasyptMap.get("password"));
		config.setAlgorithm(jasyptMap.get("algorithm"));
		config.setKeyObtentionIterations(jasyptMap.get("key-obtention-iterations"));
		config.setPoolSize(jasyptMap.get("pool-size"));
		config.setProviderName(jasyptMap.get("provider-name"));
		config.setSaltGeneratorClassName(jasyptMap.get("salt-generator-classname"));
		config.setIvGeneratorClassName(jasyptMap.get("iv-generator-classname"));
		config.setStringOutputType(jasyptMap.get("string-output-type"));
		return config;
	}

	/**
	 * 将字符串中的中直线转成驼峰式对象
	 * @author yweijian
	 * @date 2021年10月22日
	 * @version 1.0
	 * @description
	 */
	public static class middleSnakeCaseStrategy extends PropertyNamingStrategyBase {

		private static final long serialVersionUID = 896445326770807440L;

		@Override
		public String translate(String input) {
			if (input == null) {
				return input;
			}
			int length = input.length();
			StringBuilder result = new StringBuilder(length * 2);
			int resultLength = 0;
			boolean wasPrevTranslated = false;
			for (int i = 0; i < length; i++) {
				char c = input.charAt(i);
				if (i > 0 || c != '-'){
					if (Character.isUpperCase(c)) {
						if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '-') {
							result.append('-');
							resultLength++;
						}
						c = Character.toLowerCase(c);
						wasPrevTranslated = true;
					} else {
						wasPrevTranslated = false;
					}
					result.append(c);
					resultLength++;
				}
			}
			return resultLength > 0 ? result.toString() : input;
		}

	}

}
